package pl.edu.agh.ki.neuralnetwork.neurons;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import pl.edu.agh.ki.neuralnetwork.exceptions.NeuronAlreadyConnectedException;
import pl.edu.agh.ki.neuralnetwork.exceptions.NeuronNotConnectedException;

public abstract class AbstractNeuron implements InnerNeuron {

	protected Map<Neuron, Double> inputs;
	protected Map<Neuron, Double> prevInputs = new HashMap<Neuron, Double>();;
	protected Double prevBias;
	protected Double bias;
	protected Double output;
	protected Double sum;

	private Double error;
	private Bias biasN;

	/**
	 * Creates new neuron without inputs.
	 */
	public AbstractNeuron() {
		inputs = new HashMap<Neuron, Double>();
	}

	/**
	 * Create new neuron with given inputs
	 * 
	 * @param neurons
	 *            neurons list
	 * @param weights
	 *            inputs weights
	 */
	public AbstractNeuron(List<Neuron> neurons, List<Double> weights) {
		inputs = new HashMap<Neuron, Double>();
		int size = neurons.size() > weights.size() ? weights.size() : neurons
				.size();
		for (int i = 0; i < size; i++) {
			inputs.put(neurons.get(i), weights.get(i));
			prevInputs.put(neurons.get(i), null);
		}
	}

	public void addInput(Neuron neuron, double weight)
			throws NeuronAlreadyConnectedException {
		if (inputs.containsKey(neuron))
			throw new NeuronAlreadyConnectedException();
		prevInputs.put(neuron, inputs.get(neuron));
		inputs.put(neuron, weight);
	}

	public void setBias(double bias) {
		this.bias = bias;
	}

	public Double getBias() {
		return bias;
	}

	public void setPrevBias(double prevBias) {
		this.prevBias = prevBias;
	}

	public Double getPrevBias() {
		return this.prevBias;
	}

	public void setPrevWeight(Neuron neuron, double weight)
			throws NeuronNotConnectedException {
		prevInputs.put(neuron, weight);
	}

	public double getPrevWeight(Neuron neuron)
			throws NeuronNotConnectedException {
		return inputs.get(neuron);
	}

	public void setWeight(Neuron neuron, double weight)
			throws NeuronNotConnectedException {
		if (!inputs.containsKey(neuron))
			throw new NeuronNotConnectedException();
		prevInputs.put(neuron, inputs.get(neuron));
		inputs.put(neuron, weight);
	}

	public double getWeight(Neuron neuron) throws NeuronNotConnectedException {
		if (!inputs.containsKey(neuron))
			throw new NeuronNotConnectedException();
		return inputs.get(neuron);
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(this.getClass().getSimpleName() + ", connections = ");
		sb.append(inputs.size());
		sb.append(", (  ");
		for (Neuron neuron : inputs.keySet()) {
			sb.append(neuron.hashCode() + " = " + inputs.get(neuron) + "  ");
		}
		sb.append(")");
		return sb.toString();
	}

	public double getOutput() {
		if (output == null) {
			compute();
		}
		return this.output;
	}

	public void reset() {
		this.output = null;
	}

	public List<Double> getWeights() {

		return new ArrayList<Double>(inputs.values());
	}

	public List<Neuron> getInputNeurons() {
		return new ArrayList<Neuron>(inputs.keySet());
	}

	public abstract void compute();

	public void setError(Double error) {
		this.error = error;
	}

	public Double getError() {
		return error;
	}

	public Double getSum() {
		return sum;
	}

	public void setSum(Double sum) {
		this.sum = sum;
	}
	public void setBiasN(Bias bias, double d){
		this.biasN = bias;
		inputs.put(bias,d);
	}
	public Bias getBiasN(){
		return biasN;
	
	}
}